/*MyPhotoIndex Free Photo Organizer <www.myphotoindex.com>
  Copyright (C) <2006-2009>  <Asaf Yarkoni asaf@yarkoni.net>

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
using System;
using System.Collections.Generic;
using System.Text;
using MyPhotoIndex.Interfaces;
using System.Drawing;
using System.Security.Cryptography;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using MyPhotoIndex.Plugins.ImageFormatProviders.Properties;
using System.Drawing.Imaging;
using MyPhotoIndex.Utilities;
using MyPhotoIndex.Interfaces.ImageFormatProviders;
using MyPhotoIndex.Interfaces.Imaging;

namespace MyPhotoIndex.Plugins.ImageFormatProviders
{
    public class EncryptedImageFormatProvider : IImageFormatProvider, IEncryptedFormatProvider
    {
        private string m_extention;
        private List<byte[]> m_keys = new List<byte[]>();
        private Dictionary<String, Image> m_cachedThumbnails = new Dictionary<string, Image>();
        private Dictionary<String, bool> m_unAuthorized = new Dictionary<string, bool>();
        private Image ms_fileLockImage = Resources.file_lock_128;

        #region IImageFormatProvider Members

        public string Extention
        {
            get { return m_extention; }
        }

        public bool IsVideo
        {
            get { return false; }
        }

        public virtual bool IsCachable
        {
            get { return false; }
        }

        public virtual bool CanSave
        {
            get { return false; }
        }

        public EncryptedImageFormatProvider(string extention)
        {
            m_extention = extention;
        }

        public IImageMetaData GetImageMetaData(string fileName)
        {
            FormatProviderImageData imageData = new FormatProviderImageData();

            EncryptedImage encryptedImage = null;
            try
            {
                using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    encryptedImage = formatter.Deserialize(fs) as EncryptedImage;
                }
            }
            catch(Exception ex)
            {
                Logger.Log(ex);
            }

            if (encryptedImage != null)
            {
                imageData.ImageWidth = encryptedImage.ImageSize.Width;
                imageData.ImageHeight = encryptedImage.ImageSize.Height;
            }

            imageData.LastModifiedDateTime = File.GetLastWriteTimeUtc(fileName);

            return imageData;
        }

        public DateTime GetImageMediaLastUpdated(string fileName)
        {
            return File.GetLastWriteTimeUtc(fileName);
        }

        public string GetImageMediaSignature(string fileName)
        {
            return FileUtilities.GetFileMD5Signature(fileName);
        }

        public void SetImageMetaData(IImageData imageData, String fileName)
        {

        }

        public Image GetThumbnail(string fileName, Size size)
        {
            Image image = null;

            if (m_unAuthorized.ContainsKey(fileName) == true)
            {
                lock (ms_fileLockImage)
                {
                    return new Bitmap(ms_fileLockImage);
                }
            }

            if (m_cachedThumbnails.TryGetValue(fileName, out image) == false)
            {
                lock (m_cachedThumbnails)
                {
                    if (m_cachedThumbnails.TryGetValue(fileName, out image) == false)
                    {
                        try
                        {
                            image = DecryptImage(fileName, true);
                            if (image == null)
                            {
                                try
                                {
                                    m_unAuthorized.Add(fileName, true);
                                }
                                catch (Exception ex)
                                {
                                    Logger.Log(ex);
                                }

                                lock (ms_fileLockImage)
                                {
                                    return new Bitmap(ms_fileLockImage);
                                }
                            }
                        }
                        catch (ImageEncryptedException)
                        {

                        }
                        catch (Exception ex)
                        {
                            Logger.Log(ex);
                        }

                        try
                        {
                            m_cachedThumbnails.Add(fileName, image);
                        }
                        catch (Exception ex)
                        {
                            Logger.Log(ex);
                        }
                    }
                }
            }

            return new Bitmap(image);
        }

        public System.Drawing.Image LoadImage(IImageData imageData)
        {
            return LoadImage(imageData, Size.Empty);
        }

        public Image LoadImage(IImageData imageData, System.Drawing.Size requestedSize)
        {
            if (m_unAuthorized.ContainsKey(imageData.FileName) == true)
            {
                if (requestedSize.Width != 0 &&
                    requestedSize.Height != 0)
                {
                    lock (ms_fileLockImage)
                    {
                        return new Bitmap(ms_fileLockImage, requestedSize);
                    }
                }
                else
                {
                    lock (ms_fileLockImage)
                    {
                        return new Bitmap(ms_fileLockImage);
                    }
                }
            }

            Image image = null;

            try
            {
                image = DecryptImage(imageData.FileName, false);
                if (image == null)
                {
                    return null;
                }
            }
            catch (ImageEncryptedException)
            {
                return null;
            }

            if (requestedSize == Size.Empty)
            {
                return image;
            }

            using (image)
            {
                return new Bitmap(image, requestedSize);
            }
        }

        public bool CanRotate
        {
            get { return false; }
        }

        public void Rotate(string fileName, System.Drawing.RotateFlipType rotationType, out System.Drawing.Image thumbnail, out System.Drawing.Size newImageSize)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public bool Exists(string path)
        {
            return File.Exists(path);
        }

        public bool CanDelete(string path)
        {
            return Exists(path);
        }

        public void Delete(string path)
        {
            try
            {
                Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(path, Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
                     Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

        public void CopyTo(IImageData imageData, string newMediaPath)
        {
            File.Copy(imageData.FileName, newMediaPath, false);
        }

        #endregion

        private Image DecryptImage(string encImageFile, bool thumbnail)
        {
            EncryptedImage encryptedImage = null;
            using (FileStream fs = new FileStream(encImageFile, FileMode.Open, FileAccess.Read))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                encryptedImage = formatter.Deserialize(fs) as EncryptedImage;
            }

            TripleDESCryptoServiceProvider encryptionProvider = new TripleDESCryptoServiceProvider();
            byte[] imageData = null;

            foreach (byte[] key in m_keys)
            {
                byte[] shaShortKey = key;

                try
                {
                    using (ICryptoTransform transform = encryptionProvider.CreateDecryptor(shaShortKey, shaShortKey))
                    {
                        byte[] imageSource = thumbnail ? encryptedImage.Thumbnail : encryptedImage.Image;
                        imageData = transform.TransformFinalBlock(imageSource, 0, (int)imageSource.Length);
                    }
                }
                catch (Exception)
                {
                    continue;
                }

                break;
            }

            if (imageData == null)
            {
             //   throw new ImageEncryptedException();
                return null;
            }

            return ThumbnailUtilities.ThumbnailFromByteArray(imageData);
        }

        static byte[] ComputeKey(string key)
        {
            SHA512Managed sha = new SHA512Managed();

            byte[] hashKey = ASCIIEncoding.ASCII.GetBytes(key);
            byte[] shaKey = sha.ComputeHash(hashKey);
            byte[] shaShortKey = new byte[24];
            for (int ii = 0; ii < 24; ii++) shaShortKey[ii] = shaKey[ii];

            return shaShortKey;
        }

        #region IEncryptedFormatProvider Members

        public bool ValidateKey(String fileName, String key)
        {
            EncryptedImage encryptedImage = null;

            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                encryptedImage = formatter.Deserialize(fs) as EncryptedImage;
            }

            byte[] shaShortKey = ComputeKey(key);

            TripleDESCryptoServiceProvider encryptionProvider = new TripleDESCryptoServiceProvider();
            using (ICryptoTransform transform = encryptionProvider.CreateDecryptor(shaShortKey, shaShortKey))
            {
                try
                {
                    byte[] challengeData = transform.TransformFinalBlock(encryptedImage.Challenge, 0, (int)encryptedImage.Challenge.Length);
                    string dateString = ASCIIEncoding.ASCII.GetString(challengeData);

                    String compareString = encryptedImage.EncryptionDate.ToString("dd-MM-yyyy");
                    if (String.Compare(compareString, dateString) == 0)
                    {
                        return true;
                    }
                }
                catch (Exception)
                {
                    return false;
                }
            }

            return false;
        }

        public void AddKey(string key)
        {
            byte[] shaShortKey = ComputeKey(key);

            foreach (byte[] value in m_keys)
            {
                int ii = 0;
                for (ii = 0; ii < 24; ii++)
                {
                    if (value[ii] != shaShortKey[ii])
                    {
                        break;
                    }
                }

                if (ii == 24)
                {
                    return;
                }
            }

            m_keys.Add(shaShortKey);
            m_unAuthorized.Clear();
        }

        public void ClearKeys()
        {
            m_keys.Clear();

            try
            {
                foreach (Image image in m_cachedThumbnails.Values)
                {
                    image.Dispose();
                }
            }
            catch(Exception ex)
            {
                Logger.Log(ex);
            }

            m_cachedThumbnails.Clear();
        }

        public bool IsCached(string fileName)
        {
            return m_cachedThumbnails.ContainsKey(fileName);
        }

        public void Encrypt(IImageData imageData, String key)
        {
            EncryptedImage encryptedImage = new EncryptedImage();
            encryptedImage.EncryptionDate = DateTime.Now;
            encryptedImage.OriginalFileName = imageData.FileName;
            encryptedImage.ImageSize = imageData.ImageSize;

            TripleDESCryptoServiceProvider encryptionProvider = new TripleDESCryptoServiceProvider();
            byte[] shaShortKey = ComputeKey(key);

            using (Image image = Image.FromFile(encryptedImage.OriginalFileName))
            {
                Size thumbnailSize = ImageAlgo.GetImageProportionalSize(new SizeF(image.Width, image.Height), new SizeF(320, 240));

                using (Image thumbnail = new Bitmap(image, thumbnailSize))
                using (MemoryStream ms = new MemoryStream())
                {
                    thumbnail.Save(ms, ImageFormat.Jpeg);

                    using (ICryptoTransform transform = encryptionProvider.CreateEncryptor(shaShortKey, shaShortKey))
                    {
                        byte[] tempImage = ms.ToArray();
                        encryptedImage.Thumbnail = transform.TransformFinalBlock(ms.ToArray(), 0, (int)ms.Length);
                    }
                }

                using (MemoryStream ms = new MemoryStream())
                {
                    IImageFormatProvider jpgProvider = ImageFormatProviders.Default["jpg"];
                    jpgProvider.SaveImage(image, imageData, ms);

                    using (ICryptoTransform transform = encryptionProvider.CreateEncryptor(shaShortKey, shaShortKey))
                    {
                        byte[] tempImage = ms.ToArray();
                        encryptedImage.Image = transform.TransformFinalBlock(ms.ToArray(), 0, (int)ms.Length);
                    }
                }

                using (ICryptoTransform transform = encryptionProvider.CreateEncryptor(shaShortKey, shaShortKey))
                {
                    string dataString = encryptedImage.EncryptionDate.ToString("dd-MM-yyyy");
                    byte[] tempData = ASCIIEncoding.ASCII.GetBytes(dataString);
                    encryptedImage.Challenge = transform.TransformFinalBlock(tempData, 0, (int)tempData.Length);
                }
            }

            string newFile = Path.ChangeExtension(encryptedImage.OriginalFileName, "mei");
            using (FileStream fs = new FileStream(newFile, FileMode.CreateNew, FileAccess.Write))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(fs, encryptedImage);
            }

            try
            {
                File.Delete(imageData.FileName);
                imageData.FileName = newFile;
            }
            catch (Exception)
            {
            }
        }

        public bool Decrypt(IImageData imageData)
        {
            EncryptedImage encryptedImage = null;
            using (FileStream fs = new FileStream(imageData.FileName, FileMode.Open, FileAccess.Read))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                encryptedImage = formatter.Deserialize(fs) as EncryptedImage;
            }

            TripleDESCryptoServiceProvider encryptionProvider = new TripleDESCryptoServiceProvider();
            byte[] imageBytes = null;

            foreach (byte[] key in m_keys)
            {
                byte[] shaShortKey = key;

                try
                {
                    using (ICryptoTransform transform = encryptionProvider.CreateDecryptor(shaShortKey, shaShortKey))
                    {
                        byte[] imageSource = encryptedImage.Image;
                        imageBytes = transform.TransformFinalBlock(imageSource, 0, (int)imageSource.Length);
                    }
                }
                catch (Exception)
                {
                    continue;
                }

                break;
            }

            if (imageBytes == null)
            {
                //throw new ImageEncryptedException();
                return false;
            }

            using (MemoryStream ms = new MemoryStream(imageBytes))
            {
                using (Image image = Image.FromStream(ms))
                {
                    string extention = Path.GetExtension(encryptedImage.OriginalFileName);
                    extention = extention.Remove(0, 1);
                    String newFile = Path.ChangeExtension(imageData.FileName, extention);

                    IImageFormatProvider provider = ImageFormatProviders.Default[extention];
                    provider.SaveImage(image, imageData, newFile);
                    File.Delete(imageData.FileName);

                    imageData.FileName = newFile;
                }
            }

            return true;
        }

        public void SaveImage(Image image, IImageData imageData, string fileName)
        {
            throw new NotImplementedException();
        }

        public void SaveImage(Image image, IImageData imageData, Stream stream)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
